package notifier

import (
	"bytes"
	"encoding/json"
	"errors"
	"gitee.com/yysf_xin/go-common/db"
	"gitee.com/yysf_xin/go-common/tools"
	"gitee.com/yysf_xin/go-notifier/model"
	"github.com/bluele/gcache"
	"io"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"text/template"
	"time"
)

type MsgService interface {
	Init(v *model.Template, data any, opt *Option) (any, error)
	DoSend(v *model.Template, param any, opt *Option)
}

type Factory func(n *Notifier, model *model.Supplier) (MsgService, error)

type RenderFn func(w io.Writer, data any) error

type models struct {
	Supplier *model.Supplier
	Template *model.Template
}

var regMap = make(map[string]Factory)

type Notifier struct {
	Db    *db.Client
	Tr    http.RoundTripper
	cache gcache.Cache
	Tpl   *template.Template
}

type Msg struct {
	Code   string      `json:"code"`
	Target string      `json:"target"`
	Params interface{} `json:"params"`
}
type Option struct {
	Debug bool
}

func (n *Notifier) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var m Msg
	err := json.NewDecoder(r.Body).Decode(&m)
	if err != nil {
		w.WriteHeader(400)
		_, _ = w.Write(tools.StringToBytes(err.Error()))
		return
	}
	var opt Option
	opt.Debug = r.URL.Query().Get("debug") != ""
	err = n.SendByCode(m.Code, &opt, m)
	if err != nil {
		w.WriteHeader(500)
		_, _ = w.Write(tools.StringToBytes(err.Error()))
		return
	}
}

var Pool = sync.Pool{New: func() any {
	return new(bytes.Buffer)
}}

func New(client *db.Client) (c *Notifier, err error) {
	c = &Notifier{
		Db:    client,
		cache: gcache.New(10).ARC().Build(),
		Tr:    http.DefaultTransport,
		Tpl:   template.New(""),
	}
	Init(c.Tpl)
	if client.Cfg.Upgrade {
		m := models{}
		err = client.UpgradeDB(&m)
	}
	return
}
func jsonFn(input any) string {
	buff := Pool.Get().(*bytes.Buffer)
	defer Pool.Put(buff)
	buff.Reset()
	en := json.NewEncoder(buff)
	en.SetEscapeHTML(false)
	err := en.Encode(input)
	if err == nil {
		str := buff.String()
		switch input.(type) {
		case string:
			return str[1 : len(str)-2]
		}
		return buff.String()
	} else {
		tools.Log.Println(err)
	}
	return ""
}

func Init(tpl *template.Template) {
	tpl.Funcs(template.FuncMap{
		// Returns a word.  The word may or may not be in any
		// document's body.
		"json": jsonFn,
		"unix": func() string {
			return strconv.FormatInt(time.Now().UnixMilli(), 10)
		},
		"SafeTruncate": tools.SafeTruncate,
		"LastDot":      lastDot,
	})
}

func lastDot(input string) string {
	pos := strings.LastIndexByte(input, '.')
	if pos != -1 {
		pos++
		if pos >= len(input) {
			pos--
		}
		return input[pos:]
	}
	return input
}
func RegService(typeId string, service Factory) {
	regMap[typeId] = service
}

func (n *Notifier) SendByTemplate(t *model.Template, opt *Option, data any) (err error) {
	if t.SupplierId <= 0 {
		tools.Log.Println("supplier not found")
		return
	}
	svrVl, err := n.cache.Get(t.SupplierId)
	var svr MsgService
	if svrVl == nil {
		supp := new(model.Supplier)
		err = n.Db.Find(supp, t.SupplierId).Error
		if err != nil {
			return
		}
		t.Supplier = supp
		fc := regMap[supp.Type]
		if fc != nil {
			svr, err = fc(n, supp)
			if err != nil {
				return
			}
		} else {
			tools.Log.Println(t.Supplier.Type, "service type found,", supp.Type)
		}
	} else {
		svr = svrVl.(MsgService)
	}
	if svr == nil {
		tools.Log.Println(t.Supplier.Type, "service not found, id = ", t.Id)
		return
	}
	var param any
	param, err = svr.Init(t, data, opt)
	if err != nil {
		return
	}
	t.Title, err = n.renderValue(t.Code, t.Title, param)
	if err != nil {
		return
	}
	t.Url, err = n.renderValue(t.Code, t.Url, param)
	if err != nil {
		return
	}
	t.Content, err = n.renderValue(t.Code, t.Content, param)
	if err != nil {
		return
	}
	if opt != nil && opt.Debug {
		tools.Log.Println("发送参数", t.Content)
	}
	svr.DoSend(t, param, opt)
	return
}
func (n *Notifier) SendByCode(code string, opt *Option, data any) (err error) {
	var templates []*model.Template
	err = n.Db.Where("code = ? and data_flag = 1", code).Find(&templates).Error
	if err != nil {
		tools.Log.Println(err)
		return
	}
	if len(templates) == 0 {
		msg := "template not found"
		err = errors.New(msg)
		tools.Log.Println(msg)
		return
	}

	for _, v := range templates {
		err = n.SendByTemplate(v, opt, data)
		if err != nil {
			tools.Log.Println(err)
		}
	}

	return
}

const prefix = "tpl:"

func (n *Notifier) renderValue(name, tpl string, data any) (str string, err error) {
	if strings.HasPrefix(tpl, prefix) {
		tpl = tpl[len(prefix):]
	} else {
		return tpl, nil
	}
	var t *template.Template
	t, err = n.Tpl.Parse(tpl)
	if err != nil {
		tools.Log.Println("template err", err)
		return
	}
	b := Pool.Get().(*bytes.Buffer)
	defer Pool.Put(b)
	b.Reset()
	err = t.Execute(b, data)
	if err != nil {
		tools.Log.Println("template err", err)
		return
	}
	str = b.String()
	return
}
